#include "sys/unistd.h"
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <fcntl.h>
#include <assert.h>
#include <sys/time.h>

static int evtdev = -1;
static int fbdev = -1;
static int sbdev = -1;
static int sbctldev = -1;
static int screen_w = 0, screen_h = 0;
static int canvas_w = 0, canvas_h = 0;
static int canvas_x = 0, canvas_y = 0;

static struct timeval start_tv;

/**
 * https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlgetticks.html
 * Get the number of milliseconds since the SDL library initialization.
 **/
uint32_t NDL_GetTicks() {
    struct timeval cur_tv;
    gettimeofday(&cur_tv, NULL);
    return (cur_tv.tv_sec - start_tv.tv_sec) * 1000 + (cur_tv.tv_usec - start_tv.tv_usec) / 1000 ;
}

/*
 * https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlpollevent.html
 * Polls for currently pending events, and returns 1 if there are any pending events, or 0 if there are none available.
 **/
int NDL_PollEvent(char *buf, int len) {
    return read(evtdev, buf, len);
}

/**
 * 打开一张(*w) X (*h)的画布
 * 如果*w和*h均为0, 则将系统全屏幕作为画布, 并将*w和*h分别设为系统屏幕的大小
*/
void NDL_OpenCanvas(int *w, int *h) {

    // 读取 dispinfo
    char dibuf[64];
    int di_fd = open("/proc/dispinfo", O_RDONLY);
    read(di_fd, dibuf, sizeof(dibuf));
    close(di_fd);
    sscanf(dibuf, "WIDTH:%d\nHEIGHT:%d", &screen_w, &screen_h);

    // 如果*w和*h均为0, 则将系统全屏幕作为画布, 并将*w和*h分别设为系统屏幕的大小
    if (*w == 0 && *h == 0) { *w = screen_w; *h = screen_h; }
    
    // 画布大小不能超过屏幕大小
    assert(*w <= screen_w && *h <= screen_h);

    // 设置画布大小
    canvas_w = *w; canvas_h = *h;

    // 设置画布居中
    canvas_x = (screen_w - canvas_w) >> 1;
    canvas_y = (screen_h - canvas_h) >> 1;

    // 暂时不要动
    if (getenv("NWM_APP")) {
        int fbctl = 4;
        fbdev = 5;
        screen_w = *w; screen_h = *h;
        char buf[64];
        int len = sprintf(buf, "%d %d", screen_w, screen_h);
        // let NWM resize the window and create the frame buffer
        write(fbctl, buf, len);
        while (1) {
            // 3 = evtdev
            int nread = read(3, buf, sizeof(buf) - 1);
            if (nread <= 0) continue;
            buf[nread] = '\0';
            if (strcmp(buf, "mmap ok") == 0) break;
        }
        close(fbctl);
    }
}

/**
 * 向画布 (x, y) 坐标处绘制 w * h 的矩形图像, 并将该绘制区域同步到屏幕上
 * 图像像素按行优先方式存储在 pixels 中, 每个像素用32位整数以`00RRGGBB`的方式描述颜色
 * 绘制图像时, 将 Canvas 居中
*/
void NDL_DrawRect(uint32_t *pixels, int x, int y, int w, int h) {
    // 检查矩形是否超界
    assert(canvas_w >= x + w);
    assert(canvas_h >= y + h);
    
    // 按行绘制, 分批多次写入. 画布在 frame buffer 中一般不是连续存放的, 而 Rect 的源数据是连续的.
    uint32_t* p = pixels;
    int offset = (screen_w * (y + canvas_y) + (x + canvas_x)) << 2;
    for (int i = 0; i < h; ++i) {
        // 设置偏移 (按字节为单位)
        lseek(fbdev, offset, SEEK_SET);
        // 写数据 (按字节为单位)
        write(fbdev, p, w << 2);
        // 更新偏移
        offset += screen_w << 2;
        p += w;
    }
}

// 打开音频功能, 初始化声卡设备
void NDL_OpenAudio(int freq, int channels, int samples) {
    int buf[3] = {freq, channels, samples};
    write(sbctldev, (void *)buf, sizeof(buf));
}

// 关闭音频功能
void NDL_CloseAudio() {
    // 似乎没啥用
}

// 播放缓冲区`buf`中长度为`len`字节的音频数据, 返回成功播放的音频数据的字节数
int NDL_PlayAudio(void *buf, int len) {  
    return write(sbdev, buf, len);
}

// 返回当前声卡设备流缓冲区的空闲字节数
int NDL_QueryAudio() {
    int nbyte;
    read(sbctldev, &nbyte, sizeof(nbyte));
    return nbyte;
}

int NDL_Init(uint32_t flags) {

    // 初始化时间
    gettimeofday(&start_tv, NULL);

    // 初始化 event device
    evtdev = open("/dev/events", O_RDONLY);

    // 初始化 frame buffer device
    fbdev = open("/dev/fb", O_RDONLY);

    // 初始化 sbdev 和 sbctldev
    sbdev = open("/dev/sb", O_RDONLY);
    sbctldev = open("/dev/sbctl", O_RDONLY);

    // 暂时不要动
    if (getenv("NWM_APP")) {
        evtdev = 3;
    }

    return 0;
}

void NDL_Quit() {
}
